Skip to content

[clang-doc] serialize friends #146165

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 30, 2025
Merged

[clang-doc] serialize friends #146165

merged 1 commit into from
Jun 30, 2025

Conversation

evelez7
Copy link
Member

@evelez7 evelez7 commented Jun 27, 2025

Parse friends into a new FriendInfo and serialize them in JSON. We keep track of the friend declaration's template and function information if applicable.

Copy link
Member Author

evelez7 commented Jun 27, 2025

Copy link

github-actions bot commented Jun 27, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

Copy link
Contributor

@ilovepi ilovepi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, modulo formatting and 1 small suggestion.

@evelez7 evelez7 force-pushed the users/evelez7/clang-doc-precommit-friends branch from bf66a99 to 111fe87 Compare June 27, 2025 23:19
@evelez7 evelez7 force-pushed the users/evelez7/clang-doc-friends branch from a373ecb to 8dd16ca Compare June 27, 2025 23:19
@evelez7 evelez7 marked this pull request as ready for review June 30, 2025 16:50
@llvmbot
Copy link
Member

llvmbot commented Jun 30, 2025

@llvm/pr-subscribers-clang-tools-extra

Author: Erick Velez (evelez7)

Changes

Patch is 24.39 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/146165.diff

13 Files Affected:

  • (modified) clang-tools-extra/clang-doc/BitcodeReader.cpp (+46)
  • (modified) clang-tools-extra/clang-doc/BitcodeWriter.cpp (+24-3)
  • (modified) clang-tools-extra/clang-doc/BitcodeWriter.h (+5-1)
  • (modified) clang-tools-extra/clang-doc/HTMLGenerator.cpp (+3)
  • (modified) clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp (+1)
  • (modified) clang-tools-extra/clang-doc/JSONGenerator.cpp (+21-2)
  • (modified) clang-tools-extra/clang-doc/MDGenerator.cpp (+4)
  • (modified) clang-tools-extra/clang-doc/Representation.cpp (+16)
  • (modified) clang-tools-extra/clang-doc/Representation.h (+20-1)
  • (modified) clang-tools-extra/clang-doc/Serialize.cpp (+53)
  • (modified) clang-tools-extra/clang-doc/YAMLGenerator.cpp (+1)
  • (modified) clang-tools-extra/test/clang-doc/json/class.cpp (+38-38)
  • (modified) clang-tools-extra/unittests/clang-doc/BitcodeTest.cpp (+2)
diff --git a/clang-tools-extra/clang-doc/BitcodeReader.cpp b/clang-tools-extra/clang-doc/BitcodeReader.cpp
index fd6f40cff1a4e..2cbf8bf6b2879 100644
--- a/clang-tools-extra/clang-doc/BitcodeReader.cpp
+++ b/clang-tools-extra/clang-doc/BitcodeReader.cpp
@@ -94,6 +94,7 @@ static llvm::Error decodeRecord(const Record &R, InfoType &Field,
   case InfoType::IT_typedef:
   case InfoType::IT_concept:
   case InfoType::IT_variable:
+  case InfoType::IT_friend:
     Field = IT;
     return llvm::Error::success();
   }
@@ -111,6 +112,7 @@ static llvm::Error decodeRecord(const Record &R, FieldId &Field,
   case FieldId::F_child_namespace:
   case FieldId::F_child_record:
   case FieldId::F_concept:
+  case FieldId::F_friend:
   case FieldId::F_default:
     Field = F;
     return llvm::Error::success();
@@ -450,6 +452,15 @@ static llvm::Error parseRecord(const Record &R, unsigned ID,
   }
 }
 
+static llvm::Error parseRecord(const Record &R, unsigned ID, StringRef Blob,
+                               FriendInfo *F) {
+  if (ID == FRIEND_IS_CLASS) {
+    return decodeRecord(R, F->IsClass, Blob);
+  }
+  return llvm::createStringError(llvm::inconvertibleErrorCode(),
+                                 "invalid field for Friend");
+}
+
 template <typename T> static llvm::Expected<CommentInfo *> getCommentInfo(T I) {
   return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                  "invalid type cannot contain CommentInfo");
@@ -525,6 +536,18 @@ template <> llvm::Error addTypeInfo(FunctionInfo *I, FieldTypeInfo &&T) {
   return llvm::Error::success();
 }
 
+template <> llvm::Error addTypeInfo(FriendInfo *I, FieldTypeInfo &&T) {
+  if (!I->Params)
+    I->Params.emplace();
+  I->Params->emplace_back(std::move(T));
+  return llvm::Error::success();
+}
+
+template <> llvm::Error addTypeInfo(FriendInfo *I, TypeInfo &&T) {
+  I->ReturnType.emplace(std::move(T));
+  return llvm::Error::success();
+}
+
 template <> llvm::Error addTypeInfo(EnumInfo *I, TypeInfo &&T) {
   I->BaseType = std::move(T);
   return llvm::Error::success();
@@ -667,6 +690,16 @@ llvm::Error addReference(ConstraintInfo *I, Reference &&R, FieldId F) {
       "ConstraintInfo cannot contain this Reference");
 }
 
+template <>
+llvm::Error addReference(FriendInfo *Friend, Reference &&R, FieldId F) {
+  if (F == FieldId::F_friend) {
+    Friend->Ref = std::move(R);
+    return llvm::Error::success();
+  }
+  return llvm::createStringError(llvm::inconvertibleErrorCode(),
+                                 "Friend cannot contain this Reference");
+}
+
 template <typename T, typename ChildInfoType>
 static void addChild(T I, ChildInfoType &&R) {
   llvm::errs() << "invalid child type for info";
@@ -700,6 +733,9 @@ template <> void addChild(RecordInfo *I, EnumInfo &&R) {
 template <> void addChild(RecordInfo *I, TypedefInfo &&R) {
   I->Children.Typedefs.emplace_back(std::move(R));
 }
+template <> void addChild(RecordInfo *I, FriendInfo &&R) {
+  I->Friends.emplace_back(std::move(R));
+}
 
 // Other types of children:
 template <> void addChild(EnumInfo *I, EnumValueInfo &&R) {
@@ -741,6 +777,9 @@ template <> void addTemplate(FunctionInfo *I, TemplateInfo &&P) {
 template <> void addTemplate(ConceptInfo *I, TemplateInfo &&P) {
   I->Template = std::move(P);
 }
+template <> void addTemplate(FriendInfo *I, TemplateInfo &&P) {
+  I->Template.emplace(std::move(P));
+}
 
 // Template specializations go only into template records.
 template <typename T>
@@ -921,6 +960,10 @@ llvm::Error ClangDocBitcodeReader::readSubBlock(unsigned ID, T I) {
   case BI_VAR_BLOCK_ID: {
     return handleSubBlock<VarInfo>(ID, I, CreateAddFunc(addChild<T, VarInfo>));
   }
+  case BI_FRIEND_BLOCK_ID: {
+    return handleSubBlock<FriendInfo>(ID, I,
+                                      CreateAddFunc(addChild<T, FriendInfo>));
+  }
   default:
     return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                    "invalid subblock type");
@@ -1032,6 +1075,8 @@ ClangDocBitcodeReader::readBlockToInfo(unsigned ID) {
     return createInfo<FunctionInfo>(ID);
   case BI_VAR_BLOCK_ID:
     return createInfo<VarInfo>(ID);
+  case BI_FRIEND_BLOCK_ID:
+    return createInfo<FriendInfo>(ID);
   default:
     return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                    "cannot create info");
@@ -1072,6 +1117,7 @@ ClangDocBitcodeReader::readBitcode() {
     case BI_TYPEDEF_BLOCK_ID:
     case BI_CONCEPT_BLOCK_ID:
     case BI_VAR_BLOCK_ID:
+    case BI_FRIEND_BLOCK_ID:
     case BI_FUNCTION_BLOCK_ID: {
       auto InfoOrErr = readBlockToInfo(ID);
       if (!InfoOrErr)
diff --git a/clang-tools-extra/clang-doc/BitcodeWriter.cpp b/clang-tools-extra/clang-doc/BitcodeWriter.cpp
index 006ddda1b75e7..3cc0d4ad332f0 100644
--- a/clang-tools-extra/clang-doc/BitcodeWriter.cpp
+++ b/clang-tools-extra/clang-doc/BitcodeWriter.cpp
@@ -131,7 +131,8 @@ static const llvm::IndexedMap<llvm::StringRef, BlockIdToIndexFunctor>
           {BI_TEMPLATE_PARAM_BLOCK_ID, "TemplateParamBlock"},
           {BI_CONSTRAINT_BLOCK_ID, "ConstraintBlock"},
           {BI_CONCEPT_BLOCK_ID, "ConceptBlock"},
-          {BI_VAR_BLOCK_ID, "VarBlock"}};
+          {BI_VAR_BLOCK_ID, "VarBlock"},
+          {BI_FRIEND_BLOCK_ID, "FriendBlock"}};
       assert(Inits.size() == BlockIdCount);
       for (const auto &Init : Inits)
         BlockIdNameMap[Init.first] = Init.second;
@@ -224,7 +225,8 @@ static const llvm::IndexedMap<RecordIdDsc, RecordIdToIndexFunctor>
           {VAR_USR, {"USR", &genSymbolIdAbbrev}},
           {VAR_NAME, {"Name", &genStringAbbrev}},
           {VAR_DEFLOCATION, {"DefLocation", &genLocationAbbrev}},
-          {VAR_IS_STATIC, {"IsStatic", &genBoolAbbrev}}};
+          {VAR_IS_STATIC, {"IsStatic", &genBoolAbbrev}},
+          {FRIEND_IS_CLASS, {"IsClass", &genBoolAbbrev}}};
 
       assert(Inits.size() == RecordIdCount);
       for (const auto &Init : Inits) {
@@ -293,7 +295,8 @@ static const std::vector<std::pair<BlockId, std::vector<RecordId>>>
           CONCEPT_CONSTRAINT_EXPRESSION}},
         // Constraint Block
         {BI_CONSTRAINT_BLOCK_ID, {CONSTRAINT_EXPRESSION}},
-        {BI_VAR_BLOCK_ID, {VAR_NAME, VAR_USR, VAR_DEFLOCATION, VAR_IS_STATIC}}};
+        {BI_VAR_BLOCK_ID, {VAR_NAME, VAR_USR, VAR_DEFLOCATION, VAR_IS_STATIC}},
+        {BI_FRIEND_BLOCK_ID, {FRIEND_IS_CLASS}}};
 
 // AbbreviationMap
 
@@ -476,6 +479,19 @@ void ClangDocBitcodeWriter::emitBlock(const Reference &R, FieldId Field) {
   emitRecord((unsigned)Field, REFERENCE_FIELD);
 }
 
+void ClangDocBitcodeWriter::emitBlock(const FriendInfo &R) {
+  StreamSubBlockGuard Block(Stream, BI_FRIEND_BLOCK_ID);
+  emitBlock(R.Ref, FieldId::F_friend);
+  emitRecord(R.IsClass, FRIEND_IS_CLASS);
+  if (R.Template)
+    emitBlock(*R.Template);
+  if (R.Params)
+    for (const auto &P : *R.Params)
+      emitBlock(P);
+  if (R.ReturnType)
+    emitBlock(*R.ReturnType);
+}
+
 void ClangDocBitcodeWriter::emitBlock(const TypeInfo &T) {
   StreamSubBlockGuard Block(Stream, BI_TYPE_BLOCK_ID);
   emitBlock(T.Type, FieldId::F_type);
@@ -628,6 +644,8 @@ void ClangDocBitcodeWriter::emitBlock(const RecordInfo &I) {
     emitBlock(C);
   if (I.Template)
     emitBlock(*I.Template);
+  for (const auto &C : I.Friends)
+    emitBlock(C);
 }
 
 void ClangDocBitcodeWriter::emitBlock(const BaseRecordInfo &I) {
@@ -744,6 +762,9 @@ bool ClangDocBitcodeWriter::dispatchInfoForWrite(Info *I) {
   case InfoType::IT_variable:
     emitBlock(*static_cast<VarInfo *>(I));
     break;
+  case InfoType::IT_friend:
+    emitBlock(*static_cast<FriendInfo *>(I));
+    break;
   case InfoType::IT_default:
     llvm::errs() << "Unexpected info, unable to write.\n";
     return true;
diff --git a/clang-tools-extra/clang-doc/BitcodeWriter.h b/clang-tools-extra/clang-doc/BitcodeWriter.h
index f1325094f957a..d09ec4ca34006 100644
--- a/clang-tools-extra/clang-doc/BitcodeWriter.h
+++ b/clang-tools-extra/clang-doc/BitcodeWriter.h
@@ -70,6 +70,7 @@ enum BlockId {
   BI_TYPEDEF_BLOCK_ID,
   BI_CONCEPT_BLOCK_ID,
   BI_VAR_BLOCK_ID,
+  BI_FRIEND_BLOCK_ID,
   BI_LAST,
   BI_FIRST = BI_VERSION_BLOCK_ID
 };
@@ -153,6 +154,7 @@ enum RecordId {
   VAR_NAME,
   VAR_DEFLOCATION,
   VAR_IS_STATIC,
+  FRIEND_IS_CLASS,
   RI_LAST,
   RI_FIRST = VERSION
 };
@@ -169,7 +171,8 @@ enum class FieldId {
   F_type,
   F_child_namespace,
   F_child_record,
-  F_concept
+  F_concept,
+  F_friend
 };
 
 class ClangDocBitcodeWriter {
@@ -201,6 +204,7 @@ class ClangDocBitcodeWriter {
   void emitBlock(const ConceptInfo &T);
   void emitBlock(const ConstraintInfo &T);
   void emitBlock(const Reference &B, FieldId F);
+  void emitBlock(const FriendInfo &R);
   void emitBlock(const VarInfo &B);
 
 private:
diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
index c4303d287da9e..8294ff9118558 100644
--- a/clang-tools-extra/clang-doc/HTMLGenerator.cpp
+++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
@@ -987,6 +987,7 @@ llvm::Error HTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
     break;
   case InfoType::IT_concept:
   case InfoType::IT_variable:
+  case InfoType::IT_friend:
     break;
   case InfoType::IT_default:
     return llvm::createStringError(llvm::inconvertibleErrorCode(),
@@ -1018,6 +1019,8 @@ static std::string getRefType(InfoType IT) {
     return "concept";
   case InfoType::IT_variable:
     return "variable";
+  case InfoType::IT_friend:
+    return "friend";
   }
   llvm_unreachable("Unknown InfoType");
 }
diff --git a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
index c611c946b3937..7aeaa1b7cf67d 100644
--- a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
+++ b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
@@ -588,6 +588,7 @@ Error MustacheHTMLGenerator::generateDocForInfo(Info *I, raw_ostream &OS,
   case InfoType::IT_concept:
     break;
   case InfoType::IT_variable:
+  case InfoType::IT_friend:
     break;
   case InfoType::IT_default:
     return createStringError(inconvertibleErrorCode(), "unexpected InfoType");
diff --git a/clang-tools-extra/clang-doc/JSONGenerator.cpp b/clang-tools-extra/clang-doc/JSONGenerator.cpp
index 1f6167f7f9b8d..0e1a0cc347e45 100644
--- a/clang-tools-extra/clang-doc/JSONGenerator.cpp
+++ b/clang-tools-extra/clang-doc/JSONGenerator.cpp
@@ -39,8 +39,7 @@ static void serializeArray(const Container &Records, Object &Obj,
 static auto SerializeInfoLambda = [](const auto &Info, Object &Object) {
   serializeInfo(Info, Object);
 };
-static auto SerializeReferenceLambda = [](const Reference &Ref,
-                                          Object &Object) {
+static auto SerializeReferenceLambda = [](const auto &Ref, Object &Object) {
   serializeReference(Ref, Object);
 };
 
@@ -365,6 +364,22 @@ static void serializeInfo(const BaseRecordInfo &I, Object &Obj,
   Obj["IsParent"] = I.IsParent;
 }
 
+static void serializeInfo(const FriendInfo &I, Object &Obj) {
+  auto FriendRef = Object();
+  serializeReference(I.Ref, FriendRef);
+  Obj["Reference"] = std::move(FriendRef);
+  Obj["IsClass"] = I.IsClass;
+  if (I.Template)
+    serializeInfo(I.Template.value(), Obj);
+  if (I.Params)
+    serializeArray(I.Params.value(), Obj, "Params", SerializeInfoLambda);
+  if (I.ReturnType) {
+    auto ReturnTypeObj = Object();
+    serializeInfo(I.ReturnType.value(), ReturnTypeObj);
+    Obj["ReturnType"] = std::move(ReturnTypeObj);
+  }
+}
+
 static void serializeInfo(const RecordInfo &I, json::Object &Obj,
                           const std::optional<StringRef> &RepositoryUrl) {
   serializeCommonAttributes(I, Obj, RepositoryUrl);
@@ -436,6 +451,9 @@ static void serializeInfo(const RecordInfo &I, json::Object &Obj,
   if (I.Template)
     serializeInfo(I.Template.value(), Obj);
 
+  if (!I.Friends.empty())
+    serializeArray(I.Friends, Obj, "Friends", SerializeInfoLambda);
+
   serializeCommonChildren(I.Children, Obj, RepositoryUrl);
 }
 
@@ -525,6 +543,7 @@ Error JSONGenerator::generateDocForInfo(Info *I, raw_ostream &OS,
   case InfoType::IT_function:
   case InfoType::IT_typedef:
   case InfoType::IT_variable:
+  case InfoType::IT_friend:
     break;
   case InfoType::IT_default:
     return createStringError(inconvertibleErrorCode(), "unexpected info type");
diff --git a/clang-tools-extra/clang-doc/MDGenerator.cpp b/clang-tools-extra/clang-doc/MDGenerator.cpp
index 608a7f6d4a9d3..6f16f5bd2f528 100644
--- a/clang-tools-extra/clang-doc/MDGenerator.cpp
+++ b/clang-tools-extra/clang-doc/MDGenerator.cpp
@@ -378,6 +378,9 @@ static llvm::Error genIndex(ClangDocContext &CDCtx) {
       case InfoType::IT_variable:
         Type = "Variable";
         break;
+      case InfoType::IT_friend:
+        Type = "Friend";
+        break;
       case InfoType::IT_default:
         Type = "Other";
       }
@@ -472,6 +475,7 @@ llvm::Error MDGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
     break;
   case InfoType::IT_concept:
   case InfoType::IT_variable:
+  case InfoType::IT_friend:
     break;
   case InfoType::IT_default:
     return createStringError(llvm::inconvertibleErrorCode(),
diff --git a/clang-tools-extra/clang-doc/Representation.cpp b/clang-tools-extra/clang-doc/Representation.cpp
index 5b94d37d868b4..ba53329a41789 100644
--- a/clang-tools-extra/clang-doc/Representation.cpp
+++ b/clang-tools-extra/clang-doc/Representation.cpp
@@ -147,6 +147,8 @@ mergeInfos(std::vector<std::unique_ptr<Info>> &Values) {
     return reduce<ConceptInfo>(Values);
   case InfoType::IT_variable:
     return reduce<VarInfo>(Values);
+  case InfoType::IT_friend:
+    return reduce<FriendInfo>(Values);
   case InfoType::IT_default:
     return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                    "unexpected info type");
@@ -247,6 +249,15 @@ void Reference::merge(Reference &&Other) {
     Path = Other.Path;
 }
 
+bool FriendInfo::mergeable(const FriendInfo &Other) {
+  return Ref.USR == Other.Ref.USR && Ref.Name == Other.Ref.Name;
+}
+
+void FriendInfo::merge(FriendInfo &&Other) {
+  assert(mergeable(Other));
+  Ref.merge(std::move(Other.Ref));
+}
+
 void Info::mergeBase(Info &&Other) {
   assert(mergeable(Other));
   if (USR == EmptySID)
@@ -313,6 +324,8 @@ void RecordInfo::merge(RecordInfo &&Other) {
     Parents = std::move(Other.Parents);
   if (VirtualParents.empty())
     VirtualParents = std::move(Other.VirtualParents);
+  if (Friends.empty())
+    Friends = std::move(Other.Friends);
   // Reduce children if necessary.
   reduceChildren(Children.Records, std::move(Other.Children.Records));
   reduceChildren(Children.Functions, std::move(Other.Children.Functions));
@@ -422,6 +435,9 @@ llvm::SmallString<16> Info::extractName() const {
   case InfoType::IT_variable:
     return llvm::SmallString<16>("@nonymous_variable_" +
                                  toHex(llvm::toStringRef(USR)));
+                                 case InfoType::IT_friend:
+    return llvm::SmallString<16>("@nonymous_friend_" +
+                                 toHex(llvm::toStringRef(USR)));
   case InfoType::IT_default:
     return llvm::SmallString<16>("@nonymous_" + toHex(llvm::toStringRef(USR)));
   }
diff --git a/clang-tools-extra/clang-doc/Representation.h b/clang-tools-extra/clang-doc/Representation.h
index 59874f0cfcedf..fe5cc48069d58 100644
--- a/clang-tools-extra/clang-doc/Representation.h
+++ b/clang-tools-extra/clang-doc/Representation.h
@@ -46,7 +46,8 @@ enum class InfoType {
   IT_enum,
   IT_typedef,
   IT_concept,
-  IT_variable
+  IT_variable,
+  IT_friend
 };
 
 enum class CommentKind {
@@ -379,6 +380,22 @@ struct SymbolInfo : public Info {
   bool IsStatic = false;
 };
 
+struct FriendInfo : SymbolInfo {
+  FriendInfo() : SymbolInfo(InfoType::IT_friend) {}
+  FriendInfo(SymbolID USR) : SymbolInfo(InfoType::IT_friend, USR) {}
+  FriendInfo(const InfoType IT, const SymbolID &USR,
+             const StringRef Name = StringRef())
+      : SymbolInfo(IT, USR, Name) {}
+  bool mergeable(const FriendInfo &Other);
+  void merge(FriendInfo &&Other);
+
+  Reference Ref;
+  std::optional<TemplateInfo> Template;
+  std::optional<TypeInfo> ReturnType;
+  std::optional<SmallVector<FieldTypeInfo, 4>> Params;
+  bool IsClass = false;
+};
+
 struct VarInfo : SymbolInfo {
   VarInfo() : SymbolInfo(InfoType::IT_variable) {}
   explicit VarInfo(SymbolID USR) : SymbolInfo(InfoType::IT_variable, USR) {}
@@ -454,6 +471,8 @@ struct RecordInfo : public SymbolInfo {
       Bases; // List of base/parent records; this includes inherited methods and
              // attributes
 
+  std::vector<FriendInfo> Friends;
+
   ScopeChildren Children;
 };
 
diff --git a/clang-tools-extra/clang-doc/Serialize.cpp b/clang-tools-extra/clang-doc/Serialize.cpp
index 7a9cb8a1eddb9..12ef8891c720e 100644
--- a/clang-tools-extra/clang-doc/Serialize.cpp
+++ b/clang-tools-extra/clang-doc/Serialize.cpp
@@ -7,9 +7,12 @@
 //===----------------------------------------------------------------------===//
 
 #include "Serialize.h"
+#include "../clangd/CodeCompletionStrings.h"
 #include "BitcodeWriter.h"
+
 #include "clang/AST/Attr.h"
 #include "clang/AST/Comment.h"
+#include "clang/AST/DeclFriend.h"
 #include "clang/Index/USRGeneration.h"
 #include "clang/Lex/Lexer.h"
 #include "llvm/ADT/StringExtras.h"
@@ -403,6 +406,7 @@ std::string serialize(std::unique_ptr<Info> &I) {
     return serialize(*static_cast<ConceptInfo *>(I.get()));
   case InfoType::IT_variable:
     return serialize(*static_cast<VarInfo *>(I.get()));
+  case InfoType::IT_friend:
   case InfoType::IT_typedef:
   case InfoType::IT_default:
     return "";
@@ -556,6 +560,7 @@ static std::unique_ptr<Info> makeAndInsertIntoParent(ChildType Child) {
   case InfoType::IT_typedef:
   case InfoType::IT_concept:
   case InfoType::IT_variable:
+  case InfoType::IT_friend:
     break;
   }
   llvm_unreachable("Invalid reference type for parent namespace");
@@ -947,6 +952,53 @@ emitInfo(const NamespaceDecl *D, const FullComment *FC, Location Loc,
   return {std::move(NSI), makeAndInsertIntoParent<const NamespaceInfo &>(*NSI)};
 }
 
+static void parseFriends(RecordInfo &RI, const CXXRecordDecl *D) {
+  if (D->hasDefinition() && D->hasFriends())
+    for (const FriendDecl *FD : D->friends()) {
+      if (FD->isUnsupportedFriend())
+        continue;
+
+      FriendInfo F(InfoType::IT_friend, getUSRForDecl(FD));
+      const auto *ActualDecl = FD->getFriendDecl();
+      if (!ActualDecl) {
+        const auto *FriendTypeInfo = FD->getFriendType();
+        if (!FriendTypeInfo)
+          continue;
+        ActualDecl = FriendTypeInfo->getType()->getAsCXXRecordDecl();
+
+        if (!ActualDecl)
+          continue;
+        F.IsClass = true;
+      }
+
+      if (const auto *ActualTD = dyn_cast_or_null<TemplateDecl>(ActualDecl)) {
+        if (isa<RecordDecl>(ActualTD->getTemplatedDecl()))
+          F.IsClass = true;
+        F.Template.emplace();
+        for (const auto *Param : ActualTD->getTemplateParameters()->asArray())
+          F.Template->Params.emplace_back(
+              getSourceCode(Param, Param->getSourceRange()));
+        ActualDecl = ActualTD->getTemplatedDecl();
+      }
+
+      if (auto *FuncDecl = dyn_cast_or_null<FunctionDecl>(ActualDecl)) {
+        FunctionInfo TempInfo;
+        parseParameters(TempInfo, FuncDecl);
+        F.Params.emplace();
+        F.Params = std::move(TempInfo.Params);
+        F.ReturnType = getTypeInfoForType(FuncDecl->getReturnType(),
+                                          FuncDecl->getLangOpts());
+      }
+
+      F.Ref = Reference(getUSRForDecl(ActualDecl),
+                        ActualDecl->getNameAsString(), InfoType::IT_default,
+                        ActualDecl->getQualifiedNameAsString(),
+                        getInfoRelativePath(ActualDecl));
+
+      RI.Friends.push_back(std::move(F));
+    }
+}
+
 std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>>
 emitInfo(const RecordDecl *D, const FullComment *FC, Location Loc,
          bool PublicOnly) {
@@ -970,6 +1022,7 @@ emitInfo(const RecordDecl *D, const FullComment *FC, Location Loc,
     // TODO: remove first call to parseBases, that function should be deleted
     parseBases(*RI, C);
     parseBases(*RI, C, /*IsFileInRootDir=*/true, PublicOnly, /*IsParent=*/true);
+    parseFriends(*RI, C);
   }
   RI->Path = getInfoRelativePath(RI->Namespace);
 
diff --git a/clang-tools-extra/cl...
[truncated]

@evelez7 evelez7 force-pushed the users/evelez7/clang-doc-friends branch from 8dd16ca to 318f0c8 Compare June 30, 2025 18:10
@evelez7 evelez7 force-pushed the users/evelez7/clang-doc-precommit-friends branch from 111fe87 to 41027d6 Compare June 30, 2025 18:10
Base automatically changed from users/evelez7/clang-doc-precommit-friends to main June 30, 2025 18:47
@evelez7 evelez7 force-pushed the users/evelez7/clang-doc-friends branch from 318f0c8 to 6a10b9c Compare June 30, 2025 18:48
Copy link
Member Author

evelez7 commented Jun 30, 2025

Merge activity

  • Jun 30, 7:42 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Jun 30, 7:43 PM UTC: @evelez7 merged this pull request with Graphite.

@evelez7 evelez7 merged commit a68e447 into main Jun 30, 2025
7 checks passed
@evelez7 evelez7 deleted the users/evelez7/clang-doc-friends branch June 30, 2025 19:43
rlavaee pushed a commit to rlavaee/llvm-project that referenced this pull request Jul 1, 2025
Parse friends into a new FriendInfo and serialize them in JSON. We keep track of the friend declaration's template and function information if applicable.
rlavaee pushed a commit to rlavaee/llvm-project that referenced this pull request Jul 1, 2025
Parse friends into a new FriendInfo and serialize them in JSON. We keep track of the friend declaration's template and function information if applicable.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants